use anyhow::{Context, Ok, bail};
use objc2::rc::Retained;
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::NSString;

#[repr(transparent)]
#[derive(Debug, Clone, Copy)]
pub struct KeyCode(pub u16);

#[derive(Debug)]
pub(crate) struct KeyEventInfo {
    pub(crate) is_repeat: bool,

    // Represents physical key location
    // Isn't affected by keyboard layout, locale and pressed modifiers
    pub(crate) code: KeyCode,

    // Roughly speaking it's a text that will be generated by this key press.
    // It's affected by keyboard layout locale and pressed modifiers.
    // It may contain codepoint constants listed in this file,
    // e.g. for F1 it will be value of `NSF1FunctionKey` constant.
    // For dead keys it will be empty.
    pub(crate) typed_chars: Retained<NSString>,

    // Can be considered as a name of the key
    // Depends on keyboard layout but ignores modifiers
    // For keys that depend on keyboard layout it will be the symbol typed for default layer
    // For functional keys it will try to produce some meaningful codepoint, but not the same as for `characters`
    // For dead keys it will produce text from default layer
    pub(crate) key: Retained<NSString>,

    // The same as key but also takes pressed modifiers into account
    pub(crate) key_with_modifiers: Retained<NSString>,

    pub(crate) modifiers: KeyModifiersSet,
}

pub(crate) fn unpack_key_event(ns_event: &NSEvent) -> anyhow::Result<KeyEventInfo> {
    let is_repeat = ns_event.isARepeat();
    let code = ns_event.keyCode();

    let typed_chars = ns_event
        .characters()
        .with_context(|| format!("No characters field in {ns_event:?}"))?;

    let key = ns_event
        .charactersByApplyingModifiers(NSEventModifierFlags::empty())
        .with_context(|| format!("Event contains invalid data: {ns_event:?}"))?;

    // though we apply the same modifiers, it's not the same as characters
    // there are number of differences:
    // * for dead keys `characters` will be empty, but this string will contain symbol representing the key
    // * for keys like F1..F12 characters will contain codepoints from private use area defined in `KeyCodePoints`,
    // but this function will try to return some meaningful code points
    // * for all F1..F16 keys this function will return the same codepoint: \u{10} for F17 it will be empty line
    let key_with_modifiers = ns_event
        .charactersByApplyingModifiers(ns_event.modifierFlags())
        .with_context(|| format!("Event contains invalid data: {ns_event:?}"))?;

    let modifiers = ns_event.modifierFlags().into();

    let key_info = KeyEventInfo {
        is_repeat,
        code: KeyCode(code),
        typed_chars,
        key,
        key_with_modifiers,
        modifiers,
    };
    Ok(key_info)
}

#[derive(Debug)]
pub(crate) struct FlagsChangedInfo {
    pub(crate) modifiers: KeyModifiersSet,
    pub(crate) code: KeyCode,
}

pub(crate) fn unpack_flags_changed_event(ns_event: &NSEvent) -> anyhow::Result<FlagsChangedInfo> {
    if ns_event.r#type() != NSEventType::FlagsChanged {
        bail!("Unexpected type of event {ns_event:?}");
    }
    let modifiers = ns_event.modifierFlags().into();
    let code = ns_event.keyCode();

    Ok(FlagsChangedInfo {
        modifiers,
        code: KeyCode(code),
    })
}

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct KeyModifiersSet(pub usize);

impl std::fmt::Debug for KeyModifiersSet {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "KeyModifiersSet({:032b})", self.0)
    }
}

impl KeyModifiersSet {
    #[must_use]
    pub const fn contains(&self, flag: usize) -> bool {
        self.0 & flag == flag
    }
}

/// cbindgen:ignore
pub(crate) const EMPTY_KEY_MODIFIERS: KeyModifiersSet = KeyModifiersSet(0);

impl From<NSEventModifierFlags> for KeyModifiersSet {
    fn from(value: NSEventModifierFlags) -> Self {
        // We filter out device dependant part of modifier flags
        // It contains e.g. flags that allow to distinct between left and right modifier keys
        // But I'm not sure that it has the same meaning for Intel, or for different keyboards
        Self(value.bits() & NSEventModifierFlags::DeviceIndependentFlagsMask.bits())
    }
}

impl From<KeyModifiersSet> for NSEventModifierFlags {
    fn from(value: KeyModifiersSet) -> Self {
        Self(value.0)
    }
}
